\section{译：Linux 内核用户及管理员指南（节选：内存管理——概念综述）}

\subsection{虚拟内存入门}

计算机系统中的物理内存是一片有限的资源，即使是对于支持热插拔的系统而言，可装载的内存数量也有硬性限制。物理内存的分布未必连续，它可能表现为由独立的、可访问的地址范围构成的集合。另外，不同的 CPU 架构，甚至针对相同架构的不同实现对于这些地址范围如何定义都有不同的看法。

所有这些因素使得直接处理物理内存变得非常复杂，为了避开这种复杂性，人们提出了虚拟内存的概念。

虚拟内存为应用软件抽象了物理内存的细节，只在物理内存中保存必须的信息（请求分页），并提供一套机制以实现对内存的保护和对进程间数据共享的控制。

有了虚拟内存，任一次访存操作都通过虚拟地址完成。当 CPU 译码得到一条从内存中读取（或向内存中写入）操作的指令时，CPU 会将编码在指令操作数中的虚拟地址翻译成可被内存控制器识别的物理地址。

物理系统的内存被划分为页框，或页面，每个页面的大小由具体的体系结构所规定。一些架构允许使用者从一组容许的值中选取页面的大小，构建内核时可通过设置合适的内核配置选项完成对页面大小的选定。

每个物理内存页框可被映射成一个或多个虚拟内存页面。这些映射关系由页表描述，通过页表可将程序使用的虚拟内存地址翻译为物理内存地址。页表采用层次化的方式排布。

层次化页表结构中的最低一级页表包含了软件所使用的实际页面对应的物理地址，高层级的页表包含低层级页表的物理地址，顶级页表的指针记录在一个寄存器中。CPU 进行地址翻译时，会通过该寄存器访问顶级页表。虚拟地址中的最高几位用于在顶级页表中索引表项。得到的表项随后又被用于访问下一级页表，虚拟地址中的后续若干位被用作表项的索引。虚拟地址中的最低几位确定实际页面中的偏移量。

\subsection{大页面}

地址翻译需要经历数次访存，访存操作相较于 CPU 而言速度很慢。为了避免将宝贵的 CPU 时钟周期浪费在地址翻译上，CPU 将翻译的结果存放在一片缓存中，该缓存称作转址旁路缓存（Translation Lookaside Buffer，TLB）。一般而言，TLB 是非常稀缺的资源，TCB 缺失会对拥有超大内存工作集的应用程序造成很大影响。

许多现代 CPU 架构允许在高层级页表中直接映射内存页面。比如，在 x86 平台上，可以在二级和三级页表中映射 2 M 甚至 1 G 的页面，在 Linux 中，这样的页面被称作大页面。使用大页面极大地减少了 TCB 的负担，提高了 TLB 的命中率，进而提升了整个系统的性能。

Linux 中有两套机制开启将物理内存映射为大页面的功能。一种是 HugeTLB 文件系统，或称 \lstinline{hugetlbs}，这是一种伪文件系统，使用 RAM 作为后备存储空间。对于在这样的文件系统中创建的文件，数据存放在内存中，并且通过大页面进行映射。\lstinline{hugetlbfs} 在 \href{https://docs.kernel.org/5.19/admin-guide/mm/hugetlbpage.html#hugetlbpage}{Documentation/admin-guide/mm/hugetlbpage.rst} 予以介绍。

另一种较新的开启大页面的机制被称作透明大页面（Transparent Huge Pages，HTP）。\lstinline{gugetlbfs} 需要用户即系统管理员配置系统内存中的哪些部分应该且能够被映射成大页面，THP 顾名思义，则以一种对用户透明的方式管理这些映射。详情另请参阅 \href{https://docs.kernel.org/5.19/admin-guide/mm/transhuge.html#admin-guide-transhuge}{Documentation/admin-guide/mm/transhuge.rst}。

\subsection{区域}

通常，硬件限制了哪些物理内存范围可被访问。一些情况下，设备 在执行 DMA 操作时无法访问到所有的可寻址内存。其他情况下，物理内存大小超过了虚拟内存支持的最大寻址范围，而一些特殊的操作需要访问内存的某些部分。Linux 依据其可能的使用方式，将内存页面划分为若干区域。例如，\lstinline{ZONE_DMA} 会容纳可由设备 DMA 使用的内存，\lstinline{ZONE_HIGNMEM} 会容纳未被永久映射到内核地址空间的内存，\lstinline{ZONE_NORMAL} 会容纳采用一般方式寻址的页面。

由于并非所有的架构都定义了全部的内存区域，实际的内存区域布局与硬件相关，并且对 DMA 的要求对于不同的平台而言也有差异。

\subsection{结点}

很多的多处理器机器是 NUMA （Non-Uniform Memory Access，非统一内存访问）系统。这样的系统上，内存依据其举例处理器的远近被排布到具有不同访问延迟的组（bank）中。每个组都被称作一个结点（node），对于每个结点。Linux 构建了一个独立的内存管理子系统。结点拥有自己的一套区域、空闲页面表、使用中页面表以及各种数据计数器。在 \href{https://docs.kernel.org/5.19/vm/numa.html#numa}{Documentation/vm/numa.rst} 和 \href{https://docs.kernel.org/5.19/admin-guide/mm/numa_memory_policy.html#numa-memory-policy}{Documentation/admin-guide/mm/numa\_memory\_policy.rst} 中有更多的详细信息。

\subsection{页面缓存}

物理内存是易失性的，通常将数据调入内存是将数据从文件中读出。任何时候读取一个文件，数据都会被存放到页面缓存以避免因后续读取造成的巨大访问磁盘开销。类似地，当向文件执行写入操作时，数据被放置在页面缓存中，最终存放到后备存储设备中。被写入的页面被标记为脏页，当 Linux 需要复用这些页面作其他用途时，Linux 会保证将设备上的文件内容与更新后的数据同步。

\subsection{匿名内存}

匿名内存，或称匿名映射是指没有文件系统支撑的内存。这种内存映射在为程序分配堆栈空间时隐式创建，或通过显式调用 \lstinline{mmap(2)} 系统调用创建。一般来说，匿名映射只定义了进程允许访问的虚拟内存区域，对这部分内存区域的读操作会创建一个页表项，该页表项引用一个特殊的、以填 0 方式初始化的物理页框。当程序执行写操作时，会为其分配一个一般的物理页框容纳写入的数据，页面会被标记为脏页，在内核决定将该页面另作他用时，脏页会被换出。

\subsection{回收}

在系统的整个生命周期中，物理页框可被用于存储不同种类的数据，可以是内核的内部数据结构、可被设备驱动访问的 DMA 缓冲区、从文件系统中读取的数据、由用户进程分配的内存等等。

依据页面的不同使用方式，Linux 内存管理系统对页面的回收方式也不同。比如，可回收（reclaimable）页面是指可在任何时间回收的内存页面，这种内存页面缓存的数据在该页面被回收后仍可以在其他地方（如硬盘中）访问到，或是该页面可以被换出到硬盘设备中。最典型的一类可回收页面是页面缓存和匿名内存。

在大部分情况下，存放内部内核数据和被用作 DMA 缓冲区的页面是专用的，除非其使用者主动释放，否则不能移作他用。这样的页面被称作不可回收页面。但是，在特定的情况下，即便是存放内核数据结构的页面也可被回收。比如，存放在内存中的文件系统元数据缓存可以从存储设备中重新读取出来，于是乎在系统内存紧缺时就可以将这部分内存回收。

释放可回收物理内存页框和将这部分物理页面移作他用的过程被称作回收（reclaim）。根据系统的状态，Linux 可以选择以同步或异步的方式回收页面。当系统尚未加载时，大部分内存处于空闲状态，内存分配请求可以通过分配空闲的页面立即得到满足。在程序加载数量增加时，可用页面数量减少，达到一定阈值后（低水平），内存分配请求会唤醒 \lstinline{kswapd} 守护进程。该进程会以异步方式扫描内存页面，若页面中的数据在其他地方仍然可用则将其释放，或者将页面还出道后备存储设备中（还记得脏页吗？）。随着内存使用量继续增加，达到另一个阈值——最低水平，内存分配请求将触发直接回收。这种情况下，内存分配会被推迟，直到通过回收得到充足的内存页面时才会执行内存分配。

\subsection{压缩}

在系统运行过程中，任务不断地分配和释放内存，进而导致内存碎片化。尽管在虚拟内存机制下，可以将离散的的物理内存页框展现为连续的虚拟页面，不过有时候仍然需要分配一大块连续的物理内存区域。举个例子，当设备驱动器需要大块的 DMA 缓冲区，或当 THP 分配了一个大页面时，就会产生这种需求。内存压缩解决了内存碎片化问题，这种机制将位于区域低地址范围内的占用页框移动到区域的高地址范围内。压缩扫描结束时，空闲页框被组织在一起，位于区域的开始部分，进而允许分配连续的大物理页面。

像回收操作一样，压缩可以以异步方式通过 \lstinline{kcompactd} 守护进程完成，或者以同步方式由内存分配请求完成。

\subsection{OOM Killer}

实际系统中很有发生可能这样的情况：装载的机器内存耗竭，而内核此时无法通过回收获得足够的内存保持系统的正常运作。为了挽救系统系统中的剩余部分，此时会调用 OOM Killer。

为了保持整个系统的健康状况，OOM Killer 选择牺牲一项任务。选中的任务会被终止运行，其终止后释放的内存就有可能使系统继续正常运作。